This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
January 2000
Hook Up Custom Callback Functions to Create Menu Bar Help
by Deborah L. Cooper
Often, menu bar commands can be confusing to a first-time user of your application. While standard Window commands, such as File, Open, and Close are familiar, other commands may seem a little obscure, to say the least. Fortunately, with aide from a simple subclassing technique and a few API functions, you can provide a mini Help feature for menu bar commands. Using these tools, your application can display Help text when a user selects a menu item. For instance, the current window's caption area or a Status Bar control might briefly explain the selected menu item, like the message shown in Figure A. In this article, we'll show you how to create a callback function to build this feature.
Figure A: We'll hook our custom callback procedure in place of the standard window procedure to create custom menu bar help.
Addressing the nature of callbacks and subclasses
Visual Basic's AddressOf
operator, introduced in version 5, lets you pass a pointer for a user-defined sub, function, or property to an API procedure. The API function can then pass various data to the callback procedure, which the callback can manipulate as it deems appropriate. To send this pointer to an API function, you use AddressOf procedurename
as in OldWndProc = SetWindowLong (gWH, _ GWL_WNDPROC, AddressOf WindowProc)
where WindowProc
refers to a custom procedure.
Subclassing refers to a technique by which the callback function intercepts messages sent to the operating system. Because our code modifies an existing object, that is, exchanges a new procedure for the window's original procedure, the new window is said to be a subclass of the original object.
Warning: In general, callback functions must contain a specific set of arguments, which are determined by the calling API function. For a brief review of other callback requirements, read "Callback cautions" in this issue. |
Hooking a callback function into a window procedure
To create a mini-Help system for our example's menu bar, we'll use the SetWindowLong
API function to exchange, or hook, our callback function into the application's window. The SetWindowLong
function lets you change the attribute of a specified window. This API function uses the following declaration statement:
Declare Function SetWindowLong Lib _
"user32" Alias "SetWindowLongA" _
(ByVal hwnd As Long, ByVal nIndex _
As Long, ByVal dwNewLong As Long) _
As Long
To change the standard window procedure, we'll set the nIndex
argument to GWL_WNDPROC
, and dwNewLong
to the address of our new custom function. If the function succeeds, it returns a 32-bit integer that points to the previous window procedure. Table A lists some of the other attributes you can change with this function. Because each window has only one window procedure that handles all messages sent to it, our custom function will become the sole arbiter of those messages.
Table A: Attributes changed with SetWindowLong
Value | Description |
GWL_EXSTYLE | Changes extended window style |
GWL_HINSTANCE | Changes application instance handle |
GWL_ID | Changes window identifier |
GWL_STYLE | Changes window style |
GWL_USERDATA | Specifies 32-bit value that parent application uses to identify the window |
GWL_WNDPROC | Changes address for the window procedure |
Stringing along the original window procedure
Once firmly hooked in place, our custom function will determine if the application has passed the WM_MENUSELECT
message, which occurs when you select a menu item. If it has, then the function will display the appropriate Help text in the window's caption. In all cases, however the procedure will pass messages on to the parent window for normal processing. Doing so ensures that all other window operations remain the same.
To maintain the normal window message chain, we'll call the CallWindowProc
API function. This function's sole purpose is to pass message information to a specified window procedure. This function uses the following declaration:
Declare Function CallWindowProc Lib -
"user32" Alias "CallWindowProcA" _
(ByVal lpPrevWndFunc As Long, ByVal _
hwnd As Long, ByVal Msg As Long, _
ByVal wParam As Long, ByVal lParam _
As Long) As Long
where lpPrevWndFunc
contains a pointer to the previous window procedure. In our example, we'll store the pointer to the original window procedure, and then pass this pointer value in this argument. Because we're not going to alter the existing window behavior, our custom function will pass along the other arguments as is.
Unhooking a procedure
Finally, to reassign the original window procedure to the window, we'll once more use the SetWindowLong
function along with the GWL_WNDPROC
setting. This time, we'll pass the original window procedure's address in the dwNewLong
argument. Now that we've covered the basic API functions we'll use in our technique, let's put it all together in a demonstration program.
Menu bar Help with the click of a mouse button
Figure A shows our example. As you can see when you hover the mouse over, or click on, a menu item, the application displays Help text in the form's caption. To begin, create a new Visual Basic project and save it as Minihelp. Also, name the standard form frmCallbacks. Next, from Visual Basic's Projects menu, select Add Module. When VB adds Module1.BAS, add the API declarations in Listing A to it.
Listing A: SetWindowLong and CallWindowProc API declarations
|
Now, let's add the procedure that will intercept the windows messages. Listing B shows the WindowProc
procedure that we created. Enter it in the module directly below the API declarations. Notice that it also contains the LowOrd
function, which we'll explain later.
Listing B: The WindProc and LowOrd procedures
|
Create the form
For our first step, let's add the menu items. To do so, select Tools | Menu Editor. Table B shows the buttons we added to the menu bar. An arrow next to the name indicates that we made the command part of a submenu. So, for example, make the Open button a sub-item within the File menu item. Finally, we can add the code to hook the WindowProc
procedure. Add the code in Listing C to the form's code window.
Table B: Our example application's menu items
Caption | Name |
&Fishin | mnuFishin |
=>&Operatin | menuOperatin |
=>F&indin | mnuFindin |
=>&Creatin | mnuCreatin |
&Help | mnuHelp |
=>Help Off | mnuHelpOff |
&Exitin | mnuExitin |
Listing C: The example form's code
|
The callback function in action
Press [F5] to execute the demonstration program and select the Fishin menu option. When you do, Windows uses the WindowProc
function to process the message. First, it passes along all the message information on to the operating system for normal processing. Next, since the incoming message contains the WM_MENUSELECT
value, the function determines which text to place in the caption.
Whenever Windows processes a WM_MENUSELECT
message, the wParam
argument contains a word value that indicates the selected menu item's (or sub-menu item's) index. The function passes the word value to the LowOrd
function, which strips out the low-order value-the real value we need. The function then uses this value to determine which Help text to display.
To close the form, either select the Exitin option or click the form's Close button. When you do, Visual Basic unhooks the WindowProc
function from the window and restores the original window procedure.
Conclusion
In this article, we've shown you how to use the AddressOf
operator to subclass Windows messages. In addition, we've shown you how to use a custom callback function along with the API. You'll also find this same technique useful for many other Visual Basic applications that you develop.
Copyright © 2000, ZD Inc. All rights reserved. ZD Journals and the ZD Journals logo are trademarks of ZD Inc. Reproduction in whole or in part in any form or medium without express written permission of ZD Inc. is prohibited. All other product names and logos are trademarks or registered trademarks of their respective owners.